// 以下是Go语言注释的中文翻译：
// 
// ```
// 版权所有 magesfc bright.ma
//
// 根据 Apache 许可证，版本 2.0（“许可证”）授权；
// 除非遵守许可证，否则您不能使用此文件。
// 您可以在以下网址获取许可证的副本：
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，不附带任何明示或暗示的保证或条件。
// 有关特定语言管理权限和限制的信息，请参阅许可证。
// ``` 
// 
// 请注意，这种翻译保持了原文的结构和格式。
// md5:40dea2628d59d63b

// Package gitee golang 版本的 gitee API 实现 模仿 go-github 实现的
package gitee

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"sync"

	"github.com/google/go-querystring/query"
)

const (
	Version          = "v1.0.0"
	defaultBaseURL   = "https://gitee.com/api/v5/"
	defaultUserAgent = "go-gitee" + "/" + Version
)

var errNonNilContext = errors.New("context must be non-nil")

// 一个 Client 负责与 Gitee API 进行通信。 md5:3173c3c901fbb0af
type Client struct {
	clientMu sync.Mutex   // clientMu 在调用修改 CheckRedirect 函数的过程中保护 client。 md5:904c9d23fc207038
	client   *http.Client // 用于与API通信的HTTP客户端。 md5:66a9301ca4ef72a3

	// 当然，以下是该 Go 语言注释的中文翻译：
	// 
	// 当然，以下是该 Go 语言注释的中文翻译：
	// 
// API 请求的基本 URL。默认为公共 GitHub API，但可以通过设置为域名端点来与 GitHub Enterprise 配合使用。BaseURL 应始终以斜杠结尾。
// md5:2b809e86d5771784
	BaseURL *url.URL

	// 与 GitHub API 通信时使用的用户代理。 md5:31a3e16df9e1a293
	UserAgent string

	common service // 重复使用单个结构体，而不是为每个服务在堆上分配一个。 md5:3968cd7e92d357a5

	// 用于与Gitee API的不同部分进行交互的服务。 md5:6a22e5a40edd8700
	Users         *UsersService
	Repositories  *RepositoriesService
	Search        *SearchService
	PullRequests  *PullRequestsService
	Organizations *OrganizationsService
	Enterprises   *EnterprisesService
	Miscellaneous *MiscellaneousService
	Gitignores    *GitignoresService
	Issues        *IssuesService
	Git           *GitService
	Gists         *GistsService
	Activity      *ActivityService
	Licenses      *LicensesService
}

type service struct {
	client *Client
}

// Client 返回此 GitHub 客户端使用的 http.Client。 md5:890e6b9a60726356
func (c *Client) Client() *http.Client {
	c.clientMu.Lock()
	defer c.clientMu.Unlock()
	clientCopy := *c.client
	return &clientCopy
}

// 当然，以下是该Go语言注释的中文翻译：
// 
// ```go
// NewClient 返回一个新的 Gitee API 客户端。如果提供了一个空的 httpClient，
// 将会使用一个新的 http.Client。要使用需要认证的 API 方法，请提供一个能够
// 进行认证的 http.Client（例如由 golang.org/x/oauth2 库提供的客户端）。
// ```
// md5:0be37c5a30530a0b
func NewClient(httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient // 等价于 &http.Client{}
	}

	baseURL, _ := url.Parse(defaultBaseURL)

	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: defaultUserAgent}
	c.common.client = c

	c.Users = (*UsersService)(&c.common)
	c.Repositories = (*RepositoriesService)(&c.common)
	c.Search = (*SearchService)(&c.common)
	c.PullRequests = (*PullRequestsService)(&c.common)
	c.Organizations = (*OrganizationsService)(&c.common)
	c.Enterprises = (*EnterprisesService)(&c.common)
	c.Miscellaneous = (*MiscellaneousService)(&c.common)
	c.Gitignores = (*GitignoresService)(&c.common)
	c.Issues = (*IssuesService)(&c.common)
	c.Git = (*GitService)(&c.common)
	c.Gists = (*GistsService)(&c.common)
	c.Activity = (*ActivityService)(&c.common)
	c.Licenses = (*LicensesService)(&c.common)

	return c

}

// 当然，以下是Go语言注释的中文翻译：
// 
// ```go
// Response 是一个 Gitee API 响应。它封装了从 Gitee 返回的标准 http.Response，
// 并提供了方便访问分页链接等功能。
// ```
// md5:2a309ff8b3433e0b
type Response struct {
	*http.Response

// 这些字段提供了用于分页浏览结果集的页面值。对于不是分页集的一部分或没有额外页面的响应，这些字段中的任何一个或全部可以设置为零值。
//
// 这些字段支持所谓的“偏移分页”，应与 ListOptions 结构体一起使用。
// md5:9014efbafb553643
	NextPage  int
	PrevPage  int
	FirstPage int
	LastPage  int

	TotalCount int
	TotalPage  int
}

// 当然，以下是该Go语言注释的中文翻译：
// 
// newResponse 为提供的 http.Response 创建一个新的 Response。
// r 必须不为 nil。
// md5:37507211e8469499
func newResponse(r *http.Response) *Response {
	response := &Response{Response: r}
	response.populatePageValues()
	response.parseOther()
	return response
}

func (r *Response) parseOther() {
	if tc := r.Header.Get("Total_count"); tc != "" {
		r.TotalCount, _ = strconv.Atoi(tc)
	}
	if tc := r.Header.Get("Total_page"); tc != "" {
		r.TotalPage, _ = strconv.Atoi(tc)
	}
}

// populatePageValues parses the HTTP Link response headers and populates the
// various pagination link values in the Response.解析 响应头 里面 header 里面的 分页相关的值
func (r *Response) populatePageValues() {
	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
		for _, link := range strings.Split(links[0], ",") {
			segments := strings.Split(strings.TrimSpace(link), ";")
			// 当然，以下是该 Go 语言注释的中文翻译：
// 
// 链接必须至少包含 href 和 rel 属性。 md5:2afa6f642926a526
			if len(segments) < 2 {
				continue
			}
			// 确保 href 格式正确 md5:711664416a118970
			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
				continue
			}

			// 尝试提取页面参数（page 参数） md5:8ffef979b6325258
			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
			if err != nil {
				continue
			}

			q := url.Query()
			page := q.Get("page")
			perPage := q.Get("per_page")

			if page == "" && perPage == "" {
				continue
			}

			for _, segment := range segments[1:] {
				switch strings.TrimSpace(segment) {
				case `rel="next"`, `rel='next'`:
					r.NextPage, _ = strconv.Atoi(page)
				case `rel="prev"`, `rel='prev'`: //前一页
					r.PrevPage, _ = strconv.Atoi(page)
				case `rel="first"`, `rel='first'`: //第一页，这个好像总是 1 吧
					r.FirstPage, _ = strconv.Atoi(page)
				case `rel="last"`, `rel='last'`: //在 Header Link 属性中 有 next 表示下一页的page的值的，有last 表示最后一页的值的。
					r.LastPage, _ = strconv.Atoi(page)
				}
			}
		} // end for
	} // 当然，以下是Go语言注释的中文翻译：
// 
// ```go
// 如果 Response.Header["Link"] 存在，则结束
// ```
// 
// 不过，这段注释有些不完整或不够清晰。通常情况下，Go语言中的条件语句会和相应的代码块一起出现。如果你有更多的上下文或代码示例，我可以提供更准确的解释或翻译。 md5:366f293b12291a9c
}

// ```go
// NewRequest 创建一个 API 请求。可以在 urlStr 中提供相对 URL，
// 在这种情况下，它将相对于 Client 的 BaseURL 进行解析。
// 相对 URL 应始终指定为不带前导斜杠的形式。如果指定了 body，
// 则其指向的值将被 JSON 编码并作为请求体包含在内。
// ```
// md5:1cd8dd52ebe7e302
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	if !strings.HasSuffix(c.BaseURL.Path, "/") {
		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
	}

	u, err := c.BaseURL.Parse(urlStr)
	if err != nil {
		return nil, err
	}

	var buf io.ReadWriter
	if body != nil {
		buf = &bytes.Buffer{}
		enc := json.NewEncoder(buf)
		enc.SetEscapeHTML(false)
		err := enc.Encode(body)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, u.String(), buf)
	if err != nil {
		return nil, err
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Accept", "application/json")
	if c.UserAgent != "" {
		req.Header.Set("User-Agent", c.UserAgent)
	}
	return req, nil
}

// sanitizeURL 从 URL 中移除 client_secret 参数，该参数可能对用户可见。
// md5:0f248f8d9b67e906
func sanitizeURL(uri *url.URL) *url.URL {
	if uri == nil {
		return nil
	}
	params := uri.Query()
	if len(params.Get("client_secret")) > 0 {
		params.Set("client_secret", "REDACTED")
		uri.RawQuery = params.Encode()
	}
	return uri
}

type AcceptedError struct {
	// 当然，以下是Go语言注释的中文翻译：
// 
// ```go
// Raw 包含响应体。
// ```
// 
// 如果有更多需要翻译的内容或任何其他问题，请告诉我！ md5:475bd7b21da87630
	Raw []byte
}

func (*AcceptedError) Error() string {
	return "job scheduled on gitee side; try again later"
}

// Is 返回提供的错误是否等于此错误。 md5:fadec6510c1d13f5
func (ae *AcceptedError) Is(target error) bool {
	v, ok := target.(*AcceptedError)
	if !ok {
		return false
	}
	return bytes.Compare(ae.Raw, v.Raw) == 0
}

type ErrorResponse struct {
	Response *http.Response         // 导致此错误的HTTP响应 md5:297acf203fec66c9
	ErrorMap map[string]interface{} `json:"error"`   // 当然，以下是该Go语言注释的中文翻译：
// 
// 关于各个错误的更多细节 md5:d4d8d552409f9641
	Message  string                 `json:"message"` // error message
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d: %v, %v, %+v",
		r.Response.Request.Method,
		sanitizeURL(r.Response.Request.URL),
		r.Response.StatusCode,
		r.Response.Status,
		r.ErrorMap, r.Message,
	)
}

func CheckResponse(r *http.Response) error {
	if r.StatusCode == http.StatusAccepted { // = 202 等于 202 已接受。 md5:efaa7de61997937a
		return &AcceptedError{}
	}
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}

	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}

	r.Body = ioutil.NopCloser(bytes.NewBuffer(data))
	switch {
	case r.StatusCode == http.StatusUnauthorized: // 401 error
		return errorResponse
	case r.StatusCode == http.StatusForbidden: // 403 error
		return errorResponse
	default: // 以上几个报错, github 这里的报错会有 是由于api 接口访问频率限制等等
		return errorResponse
	}
}

// 当然，以下是这段Go语言注释的中文翻译：
// 
// ```go
// parseBoolResponse 根据API响应确定布尔结果。
// 几个API方法通过响应中的HTTP状态码返回布尔响应（204表示真，404表示假）。此辅助函数将确定该结果，并在存在404错误时隐藏它。任何其他错误将原样返回。
// ```
// md5:ec61fb4eeb0b51fa
func parseBoolResponse(err error) (bool, error) {
	if err == nil {
		return true, nil
	}

	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
		// 简单地说就是错误。在这种情况中，我们不传递错误。 
// 
// 注：这里的翻译稍微调整了一下以更好地符合中文的表达习惯。“Simply false” 更准确的翻译应该是“简单地说是错误”，但考虑到上下文，“Simply”在这里可能更倾向于“简而言之”，所以翻译为“简单地说”。整个句子的意思是，在特定情况下，不将错误进行传递处理。如果这段代码是用于编程场景中的错误处理部分，这句话可能是为了说明某种特殊情况下的错误处理方式。 md5:7e9387b4d03dab4f
		return false, nil
	}

	// 发生了其他一些真实的错误 md5:489f85f1b263a55c
	return false, err
}

// 以下是Go语言注释的中文翻译：
// 
// ```go
// BareDo 发送API请求，并让你处理API响应。如果发生错误或API错误，错误将包含更多信息。否则，你应该读取并关闭响应的Body。如果超出速率限制且重置时间在将来，BareDo 将立即返回 *RateLimitError 而不进行网络API调用。
//
// 提供的 ctx 不能为 nil，如果它是 nil，则返回一个错误。如果它被取消或超时，ctx.Err() 将被返回。
// ```
// md5:a1a069894659f688
func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) {
	if ctx == nil {
		return nil, errNonNilContext
	}
	resp, err := c.client.Do(req)
	if err != nil {
		// 当然，以下是该Go语言注释的中文翻译：
		// 
		// 当然，以下是该Go语言注释的中文翻译：
		// 
// 如果我们遇到了错误，并且上下文已被取消，
// 那么上下文中的错误可能更有用。
// md5:b843cf3b9eca0a00
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
		}

		// 如果错误类型是 *url.Error，在返回之前需要清理其 URL。 md5:fb46df1d46e49ad2
		if e, ok := err.(*url.Error); ok {
			if url, err := url.Parse(e.URL); err == nil {
				e.URL = sanitizeURL(url).String()
				return nil, e
			}
		}

		return nil, err
	}

	response := newResponse(resp)

	err = CheckResponse(resp)
	if err != nil { // 这里 特殊处理 AcceptedError 这种错误，提交 返回了
		defer resp.Body.Close() // 这里就提前关闭了, 其他返回的 会在调用的 地方 func Do 里面关闭

		aerr, ok := err.(*AcceptedError)
		if ok {
			b, readErr := ioutil.ReadAll(resp.Body)
			if readErr != nil {
				return response, readErr
			}

			aerr.Raw = b
			err = aerr
		}
	}
	return response, err
}

// 以下是Go语言注释的中文翻译：
// 
// ```go
// Do 发送一个API请求并返回API响应。API响应被JSON解码并存储在由v指向的值中，或者如果发生API错误，则作为错误返回。如果v实现了io.Writer接口，则原始响应体将被写入v，而不会尝试先对其进行解码。如果v为nil且没有错误发生，则响应将原样返回。如果速率限制超出且重置时间在未来，Do将立即返回*RateLimitError而不进行网络API调用。
// 
// 提供的ctx必须非空，如果它是nil，则返回错误。如果它被取消或超时，将返回ctx.Err()。
// ```
// md5:727f8b83791ba1e3
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
	resp, err := c.BareDo(ctx, req)
	if err != nil {
		return resp, err
	}
	defer resp.Body.Close()

	switch v := v.(type) {
	case nil:
	case io.Writer:
		_, err = io.Copy(v, resp.Body)
	default:
		decErr := json.NewDecoder(resp.Body).Decode(v)
		if decErr == io.EOF {
			decErr = nil // 忽略由空响应体导致的EOF错误 md5:7b1e8021256c18c3
		}
		if decErr != nil {
			err = decErr
		}
	}
	return resp, err
}

// ListOptions 指定了支持偏移分页的各种 List 方法的可选参数。
// md5:c1a5680ba32c23fc
type ListOptions struct {
	// 对于分页的结果集，要检索的页面。 md5:7b715a7134e517dd
	Page int `url:"page,omitempty"`

	// 对于分页的结果集，每页包含的结果数量。 md5:361f2396c469f300
	PerPage int `url:"per_page,omitempty"`
}

// addOptions 将 opts 中的参数作为 URL 查询参数添加到 s。opts
// 必须是一个结构体，其字段可以包含 "url" 标签。
// md5:89ac91e944a612e3
func addOptions(s string, opts interface{}) (string, error) {
	v := reflect.ValueOf(opts)
	if v.Kind() == reflect.Ptr && v.IsNil() {
		return s, nil
	}

	u, err := url.Parse(s)
	if err != nil {
		return s, err
	}

	qs, err := query.Values(opts)
	if err != nil {
		return s, err
	}

	u.RawQuery = qs.Encode()
	return u.String(), nil
}

// 当然，以下是Go语言注释的中文翻译：
// 
// ```go
// Bool 是一个辅助函数，它分配一个新的bool值来存储v，并返回指向该值的指针。
// ```
// md5:8fd7daee28119b3a
func Bool(v bool) *bool { return &v }

// 当然，以下是Go语言注释的中文翻译：
// 
// ```go
// Int 是一个辅助函数，它分配一个新的整数值来存储 v，并返回指向该值的指针。
// ```
// 
// 如果有更多需要翻译的内容或任何其他问题，请告诉我！
// md5:bc5f0e20c3eacc43
func Int(v int) *int { return &v }

// Int64 是一个辅助函数，用于分配一个新的 int64 类型的值来存储 v，并返回该值的指针。
// md5:755f304ea12e5548
func Int64(v int64) *int64 { return &v }

// 当然，以下是Go语言注释的中文翻译：
// 
// ```go
// String 是一个辅助函数，它会分配一个新的字符串值来存储 v，并返回对该字符串的指针。
// ```
// 
// 请注意，根据实际代码逻辑，这个注释可能需要进一步调整以确保其准确性。上述翻译假设这是一个辅助函数，用于为传入的值 `v` 分配一个新的字符串并返回指向该字符串的指针。如果实际情况有所不同，请提供更多的上下文信息以便更准确地翻译。
// md5:85bf305db822409b
func String(v string) *string { return &v }
